﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Linq;
using System.Text.RegularExpressions;
using WatiNCF.UnitTests;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WatiN.Core;

namespace WatiNCF.MsTest.Runner
{
    public partial class MainForm : System.Windows.Forms.Form
    {
        private ParameterizedThreadStart _runAllThreadStart;
        private Thread _runAllThread;
        private SendOrPostCallback _runAllOperationCompleted;
        private AsyncOperation _runAllOperation;
        private ToolStripMenuItem _cancelItem;
        private ToolStripMenuItem _exitMenuItem;
        private bool _cancelTestRun;
        private bool _runAllIsRunning;

        public MainForm()
        {
            InitializeComponent();
        }

        public NotifyIcon NotifyIcon
        {
            get 
            {
                UpdateTaskBar(-1);
                this.LoadMenuItems();
                timer1.Enabled = true;
                return notifyIcon1; 
            }
        }

        //must be used to destroy the icon.
        [DllImport("user32.dll", EntryPoint = "DestroyIcon")]
        static extern bool DestroyIcon(IntPtr hIcon);

        //Pass in the number
        private void UpdateTaskBar(int i)
        {
            String TaskBarLetter;
            
            // Create a graphics instance that draws to a bitmap
            Bitmap bitmap = new Bitmap(16, 16);
            SolidBrush brush = new SolidBrush(fontDialog1.Color);
            Graphics graphics = Graphics.FromImage(bitmap);

            // Draw then number of Messages to the bitmap using the user selected font
            if (i != -1)
            {
                TaskBarLetter = i.ToString();
            }
            else
            {
                TaskBarLetter = "W";
            }
            graphics.DrawString(TaskBarLetter, fontDialog1.Font, brush, 0, 0);

            // Convert the bitmap into an icon and use it for the system tray icon
            IntPtr hIcon = bitmap.GetHicon();
            Icon icon = Icon.FromHandle(hIcon);
            notifyIcon1.Icon = icon;

            //GetHicon creates an unmanaged handle which must be manually destroyed
            DestroyIcon(hIcon);
        }


        // Hide the window if we're being minimized so it doesn't show in the
        // taskbar (will still show in the system tray).
        protected override void OnResize(EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
                this.Hide();
        }

        private void LoadMenuItems()
        {
            foreach (AssemblyConfigElement assemblyConfig in ((AssemblyConfigSectionHandler)ConfigurationManager.GetSection("testrunner")).Assemblies)
            {
                ToolStripMenuItem assemblyItem = new ToolStripMenuItem(assemblyConfig.Name);
                Assembly assembly = Assembly.Load(new AssemblyName(assemblyConfig.Name));
                assemblyItem.Tag = assembly;
                AddRunAllMenuItem(assemblyItem);
                foreach (Type type in LocateTestClasses(assembly))
                {
                    string namespacePrefix = type.Namespace;
                    string prefix = type.Namespace.Substring(type.Namespace.LastIndexOf('.'), namespacePrefix.Length - type.Namespace.LastIndexOf('.')).TrimStart('.');
                    string text = prefix.Length > 0 ? string.Format("{0}.{1}", prefix, type.Name) : type.Name;
                    ToolStripMenuItem item = new ToolStripMenuItem(text);
                    item.Tag = type;
                    AddRunAllMenuItem(item);
                    foreach (MethodInfo method in LocateTestMethods(type))
                    {
                        ToolStripMenuItem methodItem = new ToolStripMenuItem(method.Name);
                        methodItem.Tag = method;
                        methodItem.Click += new EventHandler(methodItem_Click);
                        item.DropDownItems.Add(methodItem);
                    }
                    assemblyItem.DropDownItems.Add(item);
                }
                contextMenuStrip1.Items.Add(assemblyItem);
            }

            AddToolStripSeparator(contextMenuStrip1.Items);
            AddCancelMenuItem(contextMenuStrip1.Items);
            AddExitMenuItem(contextMenuStrip1.Items);
        }

        void AddToolStripSeparator(ToolStripItemCollection items)
        {
            items.Add(new ToolStripSeparator());
        }

        void AddCancelMenuItem(ToolStripItemCollection items)
        {
            _cancelItem = new ToolStripMenuItem("Cancel");
            _cancelItem.Click += new EventHandler(OnCancelItemClick);
            _cancelItem.Enabled = false;
            items.Add(_cancelItem);
        }

        void AddExitMenuItem(ToolStripItemCollection items)
        {
            _exitMenuItem = new ToolStripMenuItem("Exit");
            _exitMenuItem.Click += new EventHandler(exitToolStripMenuItem_Click);
            items.Add(_exitMenuItem);
        }

        private void OnCancelItemClick(object sender, EventArgs e)
        {
            if (_cancelTestRun)
            {
                ShowBalloonTip("Cancellation is already pending");
            }
            else
            {
                _cancelTestRun = true;
                ShowBalloonTip("Cancellation pending completion of current test");
            }
        }

        void AddRunAllMenuItem(ToolStripMenuItem parent)
        {
            ToolStripMenuItem runAllItem = new ToolStripMenuItem("Run All");
            runAllItem.Click += new EventHandler(OnRunAllItemClick);
            parent.DropDownItems.Add(runAllItem);
            parent.DropDownItems.Add(new ToolStripSeparator());
        }

        void OnRunAllItemClick(object sender, EventArgs e)
        {
            if (_runAllIsRunning)
            {
                ShowBalloonTip("Run all method is already running");
            }
            else
            {
                ShowBalloonTip("Running all WatiN tests");
                _runAllIsRunning = true;
                _cancelTestRun = false;

                EnableMenuItems(true);
                _runAllThreadStart = new ParameterizedThreadStart(RunAll);
                _runAllThread = new Thread(_runAllThreadStart);
                _runAllOperation = AsyncOperationManager.CreateOperation(null);
                _runAllOperationCompleted = new SendOrPostCallback(RunAllCompleted);
                if (_runAllThread.TrySetApartmentState(ApartmentState.STA))
                {
                    _runAllThread.Start(sender);
                }
            }
        }

        void RunAllCompleted(object sender)
        {
            if (_cancelTestRun)
            {
                ShowBalloonTip("Run All was cancelled by user");
            }
            else
            {
                ShowBalloonTip("Run All completed successfully");
            }

            _runAllIsRunning = false;
            _cancelTestRun = false;
            EnableMenuItems(false);
            
        }

        void EnableMenuItems(bool runningAllItems)
        {
            foreach (var item in contextMenuStrip1.Items)
            {
                if(item is ToolStripItem && ((ToolStripItem)item).Tag is Assembly)
                {
                    ((ToolStripItem)item).Enabled = !runningAllItems;
                }
            }
            _cancelItem.Enabled = runningAllItems;
            _exitMenuItem.Enabled = !runningAllItems;
        }

        void RunAll(object runAllItem)
        {
            if (!_cancelTestRun)
            {
                ToolStripMenuItem parent = (ToolStripMenuItem)((ToolStripMenuItem)runAllItem).OwnerItem;
                if (parent.Tag is Type || parent.Tag is Assembly)
                {
                    RunAll(parent.DropDownItems);
                }
            }

            _runAllOperation.PostOperationCompleted(_runAllOperationCompleted, null);
        }

        void RunAll(ToolStripItemCollection items)
        {
            foreach (ToolStripItem item in items)
            {
                if (!_cancelTestRun)
                {
                    if (item is ToolStripMenuItem)
                    {
                        if (item.Tag is Type)
                        {
                            RunAll(((ToolStripMenuItem) item).DropDownItems);
                        }
                        else if (item.Tag is MethodInfo)
                        {
                            ExecuteMethodItem(item);
                        }
                    }
                }
                else
                {
                    break;
                }
            }
        }

        void methodItem_Click(object sender, EventArgs e)
        {
            ExecuteMethodItem((ToolStripMenuItem)sender);
        }

        private static void ExecuteMethodItem(ToolStripItem methodItem)
        {
            try
            {
                MethodInfo method = methodItem.Tag as MethodInfo;
                if (method != null)
                {
                    IWatinTest test = (IWatinTest)Activator.CreateInstance(method.ReflectedType);
                    if (test == null)
                    {
                        throw new ApplicationException("Reflected class is not a Watin test class");
                    }

                    IE.Settings.MakeNewIeInstanceVisible = true;
                    try
                    {
                        // Try and connect to an existing session
                        test.SetShortTimeOut();
                        test.Browser = IE.AttachToIE(Find.ByUrl(new Regex(test.HomePageUrl)));
                        test.SetDefaultTimeOut();
                    }
                    catch
                    {
                        // Do nothing, we don't care if we didn't attach, just start a new session
                    }

                    test.TestInitialize();
                    method.Invoke(test, null);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error occurred: {0}", ex.ToString()));
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.Hide();

            // Cancel the close...
            e.Cancel = true;
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }


        public IEnumerable<Type> LocateTestClasses(Assembly assembly)
        {
            return assembly.GetTypes()
                .Where(c => c.GetCustomAttributes(
                                typeof(TestClassAttribute), true).Length > 0)
                .Where(c => c != typeof(BaseWatinTest<>))
                .OrderBy(c => c.FullName)
                .Select(c => c);
        }

        public IEnumerable<MethodInfo> LocateTestMethods(Type type)
        {
            return type.GetMethods(BindingFlags.Public |
                                   BindingFlags.Instance)
                .Where(m => m.GetCustomAttributes(
                                typeof(TestMethodAttribute), true).Length > 0)
                //.Where(m => m.GetCustomAttributes(
                //    typeof(IgnoreAttribute), true).Length == 0)
                .Select(m => m);
        }

        private void ShowBalloonTip(string text)
        {
            ShowBalloonTip("WatiN Test Runner", text, ToolTipIcon.Info);
        }

        private void ShowBalloonTip(string title, string text, ToolTipIcon icon)
        {
            notifyIcon1.ShowBalloonTip(3000, title, text, icon);
        }
    }
}